/* CalSyncUm 
 * Copyright (c) 2006,2007 Arthur van Bunningen
 * Based on L-dopa Calendar Sync, Copyright (c) 2006 Richard Stoner
 * 
 * This program is in no way affiliated with Google, Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/


using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Net;
using System.IO;

namespace calsyncum
{
    class GoogleInterface
    {
        /*
         * Concerning time zones: in priciple all calendarevensts should have a DateTime in 
         * localtimezone. the google interface is responsible both in receiving and sending events for adapting
         * the time to the timezone of  google. 
         * TODO: 
         *  * check with outlook in what timezone and time the appointments are in (by debugging on device)
         *  * check with google what data we get from current appointments (this can be just by looking at the xml file)
         *  * try to combine both..
         * 
         * Recurrence will work best with specified max-day..
         */
        public string gusername;
        private string gpassword;
        //private string calendarURL;
        private string authtoken;
        public bool isInitialized = true;
        
        public int maxResults = 50;
        public DateTime startMin = DateTime.MinValue;
        public DateTime startMax = DateTime.MaxValue;
        public int countAdd;
        public int countUpdate;
        public int countDel;

        

        public List<CalendarEvent> events;
        GoAmi mainForm;

        public GoogleInterface(GoAmi mainForm, string gusername, string gpassword)
        {
            events = new List<CalendarEvent>();
            this.mainForm = mainForm;
            this.gusername = gusername;
            this.gpassword = gpassword;            
            
        }
        private string getCalendarURL()
        {
            return "http://www.google.com/calendar/feeds/" + gusername + "/private/full";
        }
        

        /// <summary>
        /// Google Login Request
        /// </summary>
        /// <param name="username">google username</param>
        /// <param name="password">google account password</param>
        /// <returns>authentication token</returns>
        public bool login()
        {
            
            mainForm.debug("start login at loogle");
            //this returns the auth token
            string loginURL = "https://www.google.com/accounts/ClientLogin";

            try
            {
                //login request
                ASCIIEncoding encoding = new ASCIIEncoding();
                string postdata = "Email=" + gusername + "&Passwd=" + gpassword + "&service=cl&source=APCp-calsyncum-v2";
                byte[] data = encoding.GetBytes(postdata);

                HttpWebRequest loginrequest = (HttpWebRequest)WebRequest.Create(loginURL);
                loginrequest.ContentType = "application/x-www-form-urlencoded";
                loginrequest.Method = "POST";
                loginrequest.ContentLength = data.Length;

                Stream loginstream = loginrequest.GetRequestStream();
                loginstream.Write(data, 0, data.Length);
                loginstream.Close();

                //login response
                HttpWebResponse loginresponse = (HttpWebResponse)loginrequest.GetResponse();
                StreamReader responsestream = new StreamReader(loginresponse.GetResponseStream());
                string bufferstring = responsestream.ReadLine();
                bufferstring = responsestream.ReadLine();
                bufferstring = responsestream.ReadLine();

                responsestream.Close();
                loginresponse.Close();

                string preAuth = bufferstring;
                authtoken = preAuth.Substring(5);
                
                return true;
            }
            catch (WebException e)
            {
                mainForm.debug(e.Message);                
            }
            return false;
        }

        public bool downloadEvents()
        {
            mainForm.debug("Start download events from google");
            events.Clear();
            try
            {
                string getURL = getCalendarURL() + "?orderby=starttime&max-results=" + maxResults.ToString();
                if (startMin != DateTime.MinValue)
                {
                    getURL = getURL + "&start-min=" + startMin.Year;
                    getURL = getURL + "-";
                    if (startMin.Month < 10)
                        getURL = getURL + "0";
                    getURL = getURL + startMin.Month;
                    getURL = getURL + "-";
                    if (startMin.Day < 10)
                        getURL = getURL + "0";
                    getURL = getURL + startMin.Day;
                }
                if (startMax != DateTime.MaxValue)
                {
                    getURL = getURL + "&start-max=" + startMax.Year;
                    getURL = getURL + "-";
                    if (startMax.Month < 10)
                        getURL = getURL + "0";
                    getURL = getURL + startMax.Month;
                    getURL = getURL + "-";
                    if (startMax.Day < 10)
                        getURL = getURL + "0";
                    getURL = getURL + startMax.Day;
                }
                HttpWebRequest initialrequest = (HttpWebRequest)WebRequest.Create(getURL);
                initialrequest.ContentType = "application/x-www-form-urlencoded";
                initialrequest.Headers.Add("Authorization: GoogleLogin auth=" + authtoken);
                initialrequest.Method = "GET";
                initialrequest.AllowAutoRedirect = false;

                HttpWebResponse initialresponse = (HttpWebResponse)initialrequest.GetResponse();                
                XmlTextReader calendarresponse = new XmlTextReader(initialresponse.GetResponseStream());
                bool readhead = false;
                CalendarEvent eventholder = new CalendarEvent();
                try
                { // forgot to catch socket exception
                    while (calendarresponse.Read())
                    {
                        #region Header parsing etc
                        if (!readhead)
                        {
                            //still in the process of reading the head
                            if (calendarresponse.LocalName == "entry")
                            {
                                //this is the last item in the head of the xml file
                                readhead = true;
                            }
                        }

                        #endregion

                        if (readhead)
                        {
                            if (calendarresponse.LocalName == "entry")
                            {
                                //if we passed the entry node we should add a new entry..
                                eventholder = new CalendarEvent();
                            }
                            //begin reading items into struct

                            if (calendarresponse.Name == "title")
                            {
                                eventholder.subject = calendarresponse.ReadInnerXml();
                            }

                            if (calendarresponse.Name == "updated")
                            {
                                eventholder.updatedOnGoogle = toUniversalTime(calendarresponse.ReadInnerXml());
                            }

                            if (calendarresponse.LocalName == "id")
                            {
                                string idstring = calendarresponse.ReadInnerXml();
                                idstring = idstring.Remove(0, getCalendarURL().Length + 3);
                                eventholder.googleid = idstring;
                            }
                            if (calendarresponse.LocalName == "content")
                            {
                                eventholder.description = calendarresponse.ReadInnerXml();
                            }

                            if (calendarresponse.LocalName == "name")
                            {
                                eventholder.author = calendarresponse.ReadInnerXml();
                            }

                            if (calendarresponse.LocalName == "email")
                            {
                                eventholder.emailaddress = calendarresponse.ReadInnerXml();
                            }

                            if (calendarresponse.LocalName == "where")
                            {
                                if (calendarresponse.HasAttributes)
                                {
                                    eventholder.location = calendarresponse.GetAttribute(0).ToString();
                                }
                            }
                            if (calendarresponse.LocalName == "recurrence")
                            {
                                eventholder.hasrecurrence = true;
                            }
                            if (calendarresponse.LocalName == "originalEvent")
                            {
                                eventholder.isrecurrenceException = true;
                            }
                            if (calendarresponse.LocalName == "link")
                            {
                                // need this to delete/edit the article on google
                                if (calendarresponse.HasAttributes)
                                {
                                    if (calendarresponse.GetAttribute(0) == "edit")
                                    {
                                        eventholder.googleediturl = calendarresponse.GetAttribute(2);

                                    }
                                }

                            }

                            if (calendarresponse.LocalName == "when")
                            {

                                if (calendarresponse.HasAttributes)
                                {
                                    //tolog(calendarresponse.ToString());
                                    string startString = calendarresponse.GetAttribute("startTime");
                                    string endString = calendarresponse.GetAttribute("endTime");
                                    // an event that differens from a recurring appointment has no end-time..
                                    if (startString != null)
                                    {
                                        eventholder.startTime = toUniversalTime(startString);
                                        if (startString.IndexOf('T') == -1) eventholder.alldayevent = true;
                                    }
                                    if (endString != null)
                                    {
                                        eventholder.endTime = toUniversalTime(endString);
                                        if (eventholder.alldayevent && (endString.IndexOf('T') == -1))
                                        {
                                            eventholder.alldayevent = true;
                                        }
                                        else
                                        {
                                            eventholder.alldayevent = false;
                                        }
                                    }
                                    //add to arraylist

                                    if (endString != null)
                                    {
                                        if (eventholder.hasrecurrence == false)
                                        {
                                            if (eventholder.isrecurrenceException == false)
                                            {
                                                //recurrence exceptions are already contained in the recursion itself
                                                events.Add(eventholder);
                                            }
                                        }
                                        else
                                        {
                                            // we support a kind of recurrence we get multiple events with the same id
                                            CalendarEvent oldEvent = eventholder;
                                            events.Add(eventholder);
                                            eventholder = new CalendarEvent();
                                            CalendarEvent.copyTo(oldEvent, eventholder);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                catch (XmlException e)
                {
                    mainForm.debug(e.Message);
                    return false;
                }
                return true;
            }
            catch (WebException e)
            {
                mainForm.debug(e.Message);
                return false;
            }
        }

        public string toGoogleString(CalendarEvent calendarevent)
        {
            string bufferstring = "";
            bufferstring += "<?xml version='1.0' encoding='UTF-8'?><entry xmlns='http://www.w3.org/2005/Atom' xmlns:gd='http://schemas.google.com/g/2005'><category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2005#event'></category><title type='text'>";
            bufferstring += calendarevent.subject;
            bufferstring += "</title>";
            if (String.IsNullOrEmpty(calendarevent.description))
            {
                // bufferstring += "<content type='text'/>";
            }
            else
            {
                bufferstring += "<content type='text'>";
                bufferstring += calendarevent.description;
                bufferstring += "</content>";
            }
            /*
            bufferstring += "<author>";
            if (String.IsNullOrEmpty(calendarevent.author))
            {
                bufferstring += "<name/>";
            }
            else
            {
                bufferstring += "<name>";
                bufferstring += calendarevent.author;
                bufferstring += "</name>";
            }
            if ((calendarevent.emailaddress == null) || (calendarevent.emailaddress == ""))
            {
                bufferstring += "<email/>";
            }
            else
            {
                bufferstring += "<email>";
                bufferstring += calendarevent.emailaddress;
                bufferstring += "</email>";
            }
            bufferstring += "</author>";
            */
            bufferstring += "<gd:eventStatus value='http://schemas.google.com/g/2005#event.confirmed'></gd:eventStatus><gd:transparency value='http://schemas.google.com/g/2005#event.opaque'></gd:transparency>";
            if (String.IsNullOrEmpty(calendarevent.location))
            {
                //bufferstring += "<gd:where/>";
            }
            else
            {
                bufferstring += "<gd:where valueString='";
                bufferstring += calendarevent.location;
                bufferstring += "'></gd:where>";
            }
            bufferstring += "<gd:when startTime='";
            bufferstring += toGoogleTime(calendarevent.startTime, calendarevent.alldayevent);
            bufferstring += "' endTime='";
            bufferstring += toGoogleTime(calendarevent.endTime, calendarevent.alldayevent);
            
            bufferstring += "'></gd:when></entry>";
            return bufferstring;
        }


        /// <summary>
        /// Adds a new entry to google calendar
        /// </summary>
        /// <param name="sampleevent">xml string of the event to be added</param>
        /// <param name="feedURL">URI location of feed</param>
        /// <param name="authtoken">login token</param>
        /// <returns></returns>
        public string posttogoogle(CalendarEvent calendarevent)
        {            
            mainForm.debug("Start post to google");
            mainForm.debug("outlook id: "+ calendarevent.outlookid);
            if (!(canChange(calendarevent)))
            {
                return "";
            }

            string eventstring = toGoogleString(calendarevent);
            string updatedURL = "";
            ASCIIEncoding encoding = new ASCIIEncoding();
            byte[] data = encoding.GetBytes(eventstring);
            try
            {
                HttpWebRequest initialpost = (HttpWebRequest)WebRequest.Create(getCalendarURL());
                initialpost.ContentType = "application/atom+xml";
                initialpost.Headers.Add("Authorization: GoogleLogin auth=" + authtoken);
                initialpost.Method = "POST";
                initialpost.AllowAutoRedirect = false;
                initialpost.ContentLength = data.Length;

                Stream poststream = initialpost.GetRequestStream();
                poststream.Write(data, 0, data.Length);
                poststream.Close();

                HttpWebResponse initialresponse = (HttpWebResponse)initialpost.GetResponse();
                
                updatedURL = initialresponse.Headers.Get("location"); // this assumes that we got a redirect

                initialresponse.Close();
            }
            catch (WebException e)
            {                
                mainForm.debug(e.Message);
                return "";
            }

            //now starting on second request -> mainly to get Gid of item
            mainForm.debug("Start second request");
            try
            {
                HttpWebRequest secondrequest = (HttpWebRequest)WebRequest.Create(updatedURL);
                secondrequest.ContentType = "application/atom+xml";
                secondrequest.Headers.Add("Authorization: GoogleLogin auth=" + authtoken);
                secondrequest.Method = "POST";
                secondrequest.AllowAutoRedirect = false;
                secondrequest.ContentLength = data.Length;

                Stream poststream = secondrequest.GetRequestStream();
                poststream.Write(data, 0, data.Length);
                poststream.Close();

                HttpWebResponse secondresponse = (HttpWebResponse)secondrequest.GetResponse();
                updatedURL = secondresponse.Headers.Get("location");
                
                secondresponse.Close();

            }
            catch (WebException e)
            {                
                mainForm.debug(e.Message);
                return "";
            }
            string gidString = "";
            try
            {
                gidString = updatedURL.Remove(0, getCalendarURL().Length + 3);
            }
            catch (System.ArgumentOutOfRangeException e)
            {
                mainForm.debug(e.Message);
                return "";
            }
            countAdd += 1;
            return gidString;
        }

        public bool isOutOfQueryRange(CalendarEvent testevent)
        {
            return (((startMin != DateTime.MinValue) && (startMin.CompareTo(testevent.startTime) > 0))
                    || ((startMax != DateTime.MaxValue) && (startMax.CompareTo(testevent.startTime) < 0)));
        }

        public CalendarEvent getEventFromId(string googleid)
        {            
            if (events.Count > 0)
            {
                //make sure the gEventlist has been populated with feed items
                CalendarEvent dummyEvent;

                for (int i = 0; i < events.Count; i++)
                {
                    dummyEvent = events[i];

                    if (dummyEvent.googleid == googleid)
                    {
                        return dummyEvent;
                    }
                }

            }
            return null;
        }

        /**
         * Update on google, will keep id..
         */
        public bool updateongoogle(string oldgoogleid, CalendarEvent calendarevent)
        {
            
            mainForm.debug("Start update on google");
            mainForm.debug("outlook id: " + calendarevent.outlookid);
            if (!(canChange(calendarevent)))
            {
                return false;
            }
            CalendarEvent oldgoogleEvent = getEventFromId(oldgoogleid);
            if (oldgoogleEvent == null)
            {
                mainForm.debug("G: No old calendar event, could not update");
                return false;
            }
            string editurl = oldgoogleEvent.googleediturl;
            if (String.IsNullOrEmpty(editurl))
            {
                mainForm.debug("G: No edit url, could not update");
                return false;
            }
            string eventstring = toGoogleString(calendarevent);
            ASCIIEncoding encoding = new ASCIIEncoding();
            string updatedURL;
            byte[] data = encoding.GetBytes(eventstring);
            try
            {
                HttpWebRequest initialpost = (HttpWebRequest)WebRequest.Create(editurl);
                initialpost.ContentType = "application/atom+xml";
                initialpost.Headers.Add("Authorization: GoogleLogin auth=" + authtoken);
                initialpost.Method = "PUT";
                initialpost.AllowAutoRedirect = false;
                initialpost.ContentLength = data.Length;

                Stream poststream = initialpost.GetRequestStream();
                poststream.Write(data, 0, data.Length);
                poststream.Close();

                HttpWebResponse initialresponse = (HttpWebResponse)initialpost.GetResponse();

                updatedURL = initialresponse.Headers.Get("location"); // this assumes that we got a redirect

                initialresponse.Close();
            }
            catch (WebException e)
            {
                mainForm.debug(e.Message);
                return false;
            }
            //now starting on second request -> mainly to get Gid of item
            mainForm.debug("Start second request");
            try
            {
                HttpWebRequest secondrequest = (HttpWebRequest)WebRequest.Create(updatedURL);
                secondrequest.ContentType = "application/atom+xml";
                secondrequest.Headers.Add("Authorization: GoogleLogin auth=" + authtoken);
                secondrequest.Method = "PUT";
                secondrequest.AllowAutoRedirect = false;
                secondrequest.ContentLength = data.Length;

                Stream poststream = secondrequest.GetRequestStream();
                poststream.Write(data, 0, data.Length);
                poststream.Close();

                HttpWebResponse secondresponse = (HttpWebResponse)secondrequest.GetResponse();
                updatedURL = secondresponse.Headers.Get("location");

                secondresponse.Close();

            }
            catch (WebException e)
            {
                mainForm.debug(e.Message);
                return false;
            }
            countUpdate += 1;
            return true;
            
        }

        public bool canChange(CalendarEvent calendarevent)
        {
            if (calendarevent == null)
            {
                mainForm.debug("Event is null");
                return false;
            }
            if (isOutOfQueryRange(calendarevent))
            {
                mainForm.debug("Event out of query range");
                return false;
            }
            if (calendarevent.hasrecurrence)
            {
                mainForm.debug("Event has recurrence");
                return false;
            }
            return true;
        }

        public bool deleteongoogle(string googleid)
        {            
            // ASCIIEncoding encoding = new ASCIIEncoding();
            //byte[] data = encoding.GetBytes(sampleevent);
            mainForm.debug("Start delete on google");
            mainForm.debug("google id: " + googleid);
            CalendarEvent currEvent = getEventFromId(googleid);
            if (!(canChange(currEvent)))
            {
                return false;
            }
            string editurl = currEvent.googleediturl;
            if (String.IsNullOrEmpty(editurl))
            {
                mainForm.debug("G: No edit url, could not delete");
                return false;
            }             
            string updatedURL = "";
            try
            {
                HttpWebRequest initialpost = (HttpWebRequest)WebRequest.Create(editurl);
                initialpost.ContentType = "application/atom+xml";
                initialpost.Headers.Add("Authorization: GoogleLogin auth=" + authtoken);
                initialpost.Method = "DELETE";
                initialpost.AllowAutoRedirect = false;

                HttpWebResponse initialresponse = (HttpWebResponse)initialpost.GetResponse();
                //tolog(initialresponse.Headers.Get("location"));
                updatedURL = initialresponse.Headers.Get("location");
                initialresponse.Close();

                mainForm.debug("Start second delete on google");

                HttpWebRequest whyagain = (HttpWebRequest)WebRequest.Create(updatedURL);
                whyagain.ContentType = "application/atom+xml";
                whyagain.Headers.Add("Authorization: GoogleLogin auth=" + authtoken);
                whyagain.Method = "DELETE";

                HttpWebResponse secondresponse = (HttpWebResponse)whyagain.GetResponse();
                secondresponse.Close();
                countDel += 1;
                return true;
            }
            catch (WebException e)
            {
                mainForm.debug(e.Message);                
            }
            
            return false;
        }

        public static DateTime toUniversalTime(string googleTime)
        {
            // 2006-09-19T09:00:00.000+02:00
            // The UTC time is equal to the local time minus the UTC offset.
            return DateTime.Parse(googleTime).ToUniversalTime();
        }

        public static string toGoogleTime(DateTime universalTime, bool allday)
        {
            if (allday)
            {
                universalTime = universalTime.ToLocalTime();
            }

            //DateTime universal = input.ToUniversalTime();
            string hours = universalTime.Hour.ToString();
            string minutes = universalTime.Minute.ToString();
            string seconds = universalTime.Second.ToString();
            string month = universalTime.Month.ToString();
            string day = universalTime.Day.ToString();
            string year = universalTime.Year.ToString();

            if (universalTime.Month < 10)
                month = "0" + month;

            if (universalTime.Day < 10)
                day = "0" + day;

            if (universalTime.Hour < 10)
                hours = "0" + hours;

            if (universalTime.Minute < 10)
                minutes = "0" + minutes;

            if (universalTime.Second < 10)
                seconds = "0" + seconds;


            string tempoffset = "+00:00"; // since internal times are in utc we have no offset :)
            string output;
            if (allday)
            {
                output = year + "-" + month + "-" + day+tempoffset; 
            }
            else
            {
                output = year + "-" + month + "-" + day + "T" + hours + ":" + minutes + ":" + seconds + ".000" + tempoffset;// "-04:00"; 
            }
            return output;
        }

    }
  

}
